package io.kiki.stack.http.feign.template;

import io.kiki.stack.http.feign.Util;
import io.kiki.stack.http.feign.template.Template.EncodingOptions;
import io.kiki.stack.http.feign.template.Template.ExpansionOptions;

import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * Template for HTTP Headers. Variables that are unresolved are ignored and Literals are not
 * encoded.
 */
public final class HeaderTemplate {

    private final String name;
    private final List<Template> values = new CopyOnWriteArrayList<>();

    /**
     * Create a new Header Template.
     *
     * @param name    of the Header.
     * @param values  for the Header.
     * @param charset to use when encoding the values.
     */
    private HeaderTemplate(String name, Iterable<String> values, Charset charset) {
        this(name, values, charset, false);
    }

    /**
     * Create a new Header Template.
     *
     * @param name    of the header
     * @param values  of the header
     * @param charset for the header
     * @param literal indicator. Will treat all values as literals instead of possible expressions.
     */
    private HeaderTemplate(String name, Iterable<String> values, Charset charset, boolean literal) {
        this.name = name;

        for (String value : values) {
            if (value == null || value.isEmpty()) {
                /* skip */
                continue;
            }

            if (literal) {
                this.values.add(new Template(ExpansionOptions.ALLOW_UNRESOLVED, EncodingOptions.NOT_REQUIRED, false, charset, Collections.singletonList(Literal.create(value))));
            } else {
                this.values.add(new Template(value, ExpansionOptions.REQUIRED, EncodingOptions.NOT_REQUIRED, false, charset));
            }
        }
    }

    public static HeaderTemplate create(String name, Iterable<String> values) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("name is required.");
        }

        if (values == null) {
            throw new IllegalArgumentException("values are required");
        }

        return new HeaderTemplate(name, values, Util.UTF_8);
    }

    public static HeaderTemplate literal(String name, Iterable<String> values) {
        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("name is required.");
        }

        if (values == null) {
            throw new IllegalArgumentException("values are required");
        }

        return new HeaderTemplate(name, values, Util.UTF_8, true);
    }

    /**
     * Append values to a Header Template.
     *
     * @param headerTemplate to append to.
     * @param values         to append.
     * @return a new Header Template with the values added.
     */
    public static HeaderTemplate append(HeaderTemplate headerTemplate, Iterable<String> values) {
        LinkedHashSet<String> headerValues = new LinkedHashSet<>(headerTemplate.getValues());
        headerValues.addAll(StreamSupport.stream(values.spliterator(), false).filter(Util::isNotBlank).collect(Collectors.toCollection(LinkedHashSet::new)));
        return create(headerTemplate.getName(), headerValues);
    }

    /**
     * Append values to a Header Template, as literals
     *
     * @param headerTemplate to append to.
     * @param values         to append.
     * @return a new Header Template with the values added.
     */
    public static HeaderTemplate appendLiteral(HeaderTemplate headerTemplate, Iterable<String> values) {
        LinkedHashSet<String> headerValues = new LinkedHashSet<>(headerTemplate.getValues());
        headerValues.addAll(StreamSupport.stream(values.spliterator(), false).filter(Util::isNotBlank).collect(Collectors.toCollection(LinkedHashSet::new)));
        return literal(headerTemplate.getName(), headerValues);
    }

    public Collection<String> getValues() {
        return Collections.unmodifiableList(this.values.stream().map(Template::toString).collect(Collectors.toList()));
    }

    public List<String> getVariables() {
        List<String> variables = new ArrayList<>();
        for (Template template : this.values) {
            variables.addAll(template.getVariables());
        }
        return Collections.unmodifiableList(variables);
    }

    public String getName() {
        return this.name;
    }

    public String expand(Map<String, ?> variables) {
        List<String> expanded = new ArrayList<>();
        if (!this.values.isEmpty()) {
            for (Template template : this.values) {
                String result = template.expand(variables);

                if (result == null) {
                    /* ignore unresolved values */
                    continue;
                }

                expanded.add(result);
            }
        }

        StringBuilder result = new StringBuilder();
        if (!expanded.isEmpty()) {
            result.append(String.join(", ", expanded));
        }

        return result.toString();
    }
}
